#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <sstream>
#include <list>

#define INFILENAME_ARG  1
#define OUTFILENAME_ARG 2
#define STOPWORDSFILENAME_ARG 3

std::string inFileName;
std::string outFileName;
std::string stopWordsFileName;

void invalid_args()
{
	std::cerr << "Argumentos Inválidos."  << std::endl;
	std::cerr << "Uso: normalizePhrases infile outfile stopwordsfile" << std::endl;
	exit(1);
}

void parse_arguments( int argc, char* argv[] )
{
	if ( argc < 4 )
		invalid_args();
		
	inFileName  = std::string( argv[INFILENAME_ARG]  );
	outFileName = std::string( argv[OUTFILENAME_ARG] );
	stopWordsFileName = std::string ( argv[STOPWORDSFILENAME_ARG] );
}

/**
 * Clase Phrase
 *
 * Sirve para leer frases con autor (nombre opcional)
 *  de un archivo de texto tipo csv ( campos separados
 *  por token )
 *
 */
class Phrase
{

private:

    static char defaultToken;

	std::string phrase;
	std::string authorSurname;
	std::string authorName;
	
public:

	Phrase()
    {}

	Phrase( Phrase const &original )
	{
	    phrase        = original.phrase;
	    authorName    = original.authorName;
	    authorSurname = original.authorSurname;
	}
	
	bool fromString( std::string const &line , char token = defaultToken )
	{
	    bool parserSuccess;

	    std::stringstream ss( line );

        authorName.clear();

	    if ( getline( ss, authorSurname, token ) &&
             getline( ss, authorName, token ) &&
             getline( ss, phrase, token ) )
	    {
	        parserSuccess = true;
	    }
	    else
	    {
	        parserSuccess = false;
	    }

	    return parserSuccess;
	}
	
	std::string toString( char &token = defaultToken ) const
	{
	    std::stringstream ss;

	    ss << authorSurname << token << authorName << token << phrase;

	    return ss.str();
	}
	
	std::string getPhrase()
	{
	    return phrase;
	}

	std::string getAuthorName()
	{
	    return authorName;
	}
	
	std::string getAuthorSurname()
	{
	    return authorSurname;
	}

	char getToken()
	{
	    return defaultToken;
	}
};

char Phrase::defaultToken = '|';

/**
 * Estructura invalid symbol
 * Representa un caracter inválido (tildes y demás)
 *  y tiene un posible reemplazo para volverlo valido
 */
struct invalidSymbol
{
    std::string m_symbol;
    std::string m_replacement;

    invalidSymbol( std::string const &symbol, std::string const &replacement ) :
        m_symbol( symbol ),
        m_replacement( replacement )
    {
    }
};

class InvalidSymbolsDictionary
{
private:
    typedef std::list < invalidSymbol > invalidSymbolsCollection;

public:
    typedef invalidSymbolsCollection::iterator iterator;

public:
    InvalidSymbolsDictionary()
    { initialize(); };

    iterator begin()
    {
        return invalidSymbols.begin();
    }

    iterator end()
    {
        return invalidSymbols.end();
    }

private:
    invalidSymbolsCollection invalidSymbols;

    void initialize()
    {
        invalidSymbols.push_back( invalidSymbol( "á", "a" ) );
        invalidSymbols.push_back( invalidSymbol( "à", "a" ) );
        invalidSymbols.push_back( invalidSymbol( "â", "a" ) );
        invalidSymbols.push_back( invalidSymbol( "ä", "a" ) );

        invalidSymbols.push_back( invalidSymbol( "é", "e" ) );
        invalidSymbols.push_back( invalidSymbol( "è", "e" ) );
        invalidSymbols.push_back( invalidSymbol( "ê", "e" ) );
        invalidSymbols.push_back( invalidSymbol( "ë", "e" ) );

        invalidSymbols.push_back( invalidSymbol( "í", "i" ) );
        invalidSymbols.push_back( invalidSymbol( "ì", "i" ) );
        invalidSymbols.push_back( invalidSymbol( "î", "i" ) );
        invalidSymbols.push_back( invalidSymbol( "ï", "i" ) );

        invalidSymbols.push_back( invalidSymbol( "ó", "o" ) );
        invalidSymbols.push_back( invalidSymbol( "ò", "o" ) );
        invalidSymbols.push_back( invalidSymbol( "ô", "o" ) );
        invalidSymbols.push_back( invalidSymbol( "ö", "o" ) );

        invalidSymbols.push_back( invalidSymbol( "ú", "u" ) );
        invalidSymbols.push_back( invalidSymbol( "ù", "u" ) );
        invalidSymbols.push_back( invalidSymbol( "û", "u" ) );
        invalidSymbols.push_back( invalidSymbol( "ü", "u" ) );

        invalidSymbols.push_back( invalidSymbol( ",", " " ) );
        invalidSymbols.push_back( invalidSymbol( ".", " " ) );
        invalidSymbols.push_back( invalidSymbol( ";", " " ) );
        invalidSymbols.push_back( invalidSymbol( ":", " " ) );
        invalidSymbols.push_back( invalidSymbol( "?", " " ) );
        invalidSymbols.push_back( invalidSymbol( "|", " " ) );
        invalidSymbols.push_back( invalidSymbol( "¿", " " ) );
        invalidSymbols.push_back( invalidSymbol( "!", " " ) );
        invalidSymbols.push_back( invalidSymbol( "¡", " " ) );
        invalidSymbols.push_back( invalidSymbol( "[", " " ) );
        invalidSymbols.push_back( invalidSymbol( "]", " " ) );
        invalidSymbols.push_back( invalidSymbol( "(", " " ) );
        invalidSymbols.push_back( invalidSymbol( ")", " " ) );
        invalidSymbols.push_back( invalidSymbol( "<", " " ) );
        invalidSymbols.push_back( invalidSymbol( ">", " " ) );
        invalidSymbols.push_back( invalidSymbol( "'", " " ) );
        invalidSymbols.push_back( invalidSymbol( "/", " " ) );
        invalidSymbols.push_back( invalidSymbol( "-", " " ) );
        invalidSymbols.push_back( invalidSymbol( "~", " " ) );
        invalidSymbols.push_back( invalidSymbol( "$", " " ) );
        invalidSymbols.push_back( invalidSymbol( "%", " " ) );
        invalidSymbols.push_back( invalidSymbol( "#", " " ) );
        invalidSymbols.push_back( invalidSymbol( "@", " " ) );
        invalidSymbols.push_back( invalidSymbol( "º", " " ) );
        invalidSymbols.push_back( invalidSymbol( "ª", " " ) );
        invalidSymbols.push_back( invalidSymbol( "*", " " ) );
        invalidSymbols.push_back( invalidSymbol( "+", " " ) );
        invalidSymbols.push_back( invalidSymbol( "^", " " ) );
        invalidSymbols.push_back( invalidSymbol( "¬", " " ) );
        invalidSymbols.push_back( invalidSymbol( "½", " " ) );
        invalidSymbols.push_back( invalidSymbol( "¨", " " ) );
        invalidSymbols.push_back( invalidSymbol( "…", " " ) );
        invalidSymbols.push_back( invalidSymbol( "\"", " " ) );
        invalidSymbols.push_back( invalidSymbol( "\\", " " ) );
    }

};

InvalidSymbolsDictionary invalidSymbolsDictionary;

/**
 * Función que reemplaza cada caracter considerado "simbolo extraño"
 *  por un "equivalente aceptable"
 *
 * @param str
 */
void replaceSymbols( std::string &str )
{
    InvalidSymbolsDictionary::iterator it = invalidSymbolsDictionary.begin();

    for ( ; it!= invalidSymbolsDictionary.end(); it++ )
    {
        size_t pos = str.find( it->m_symbol );
        while ( pos != std::string::npos )
        {
            str.replace( pos, it->m_symbol.size(), it->m_replacement );
            pos = str.find( it->m_symbol );
        }
    }
}

/**
 *  Función personalizada que pasa un string a minúsculas
 *  Nota: es personalizada porque tolower parece que rompia las ñ
 *
 * @param str
 */
void toLower( std::string &str )
{
    std::string upper_enie("Ñ");
    std::string lower_enie("ñ");

    // reemplazo las Ñ por ñ
    size_t pos = str.find( upper_enie );
    while ( pos != std::string::npos )
    {
        str.replace( pos, upper_enie.size(), lower_enie );
        pos = str.find( upper_enie );
    }

    // ahora aplico tolower a las demás letras
    for( std::string::iterator it = str.begin(); it != str.end(); it++ )
    {
        if ( *it <= 'Z' && *it >= 'A' )
            *it -= 'Z' - 'z';
    }
}

/**
 * Función encargada de realizar la normalización de palabras
 *
 * @param word
 * @return
 */
std::string normalize( std::string word )
{
    std::string normalizedWord(word);
    std::stringstream ss;


    // paso las letras de la palabra a minúsculas
    toLower( normalizedWord );

    // realizo reemplazos de tildes y otros símbolos
    replaceSymbols( normalizedWord );

    return normalizedWord;
}

/**
 *  Esta clase es un diccionario de stop-words
 *  Se utiliza para filtrar stop-words
 */
class StopWordsDictionary
{
  std::list < std::string > stopWords;

public:
  void load( std::ifstream &ifs )
  {
      std::string lineBuffer;

      while ( getline( ifs, lineBuffer ) )
      {
          stopWords.push_back(lineBuffer);
      }
  }

  bool isStopWord( std::string &str )
  {
      for( std::list < std::string >::iterator it = stopWords.begin(); it!= stopWords.end(); it++ )
      {
          if ( it->compare( str ) == 0 )
              return true;
      }

      return false;
  }

};

int main ( int argc, char* argv[] )
{
	parse_arguments( argc, argv );

	std::ifstream infile;
    std::ifstream stopwordsfile;
	std::ofstream outfile;
	std::string lineBuffer;
	Phrase phraseBuffer;
	StopWordsDictionary stopWordsDictionary;
	
	infile.open( inFileName.c_str() );
	outfile.open( outFileName.c_str() );
	stopwordsfile.open( stopWordsFileName.c_str() );

	stopWordsDictionary.load( stopwordsfile );

	while ( getline( infile, lineBuffer ) )
	{
		phraseBuffer.fromString( lineBuffer );

		    // normalizo la palabra frase
		std::string normalizedPhrase = normalize( phraseBuffer.getPhrase() );

		std::stringstream ss( normalizedPhrase );
		std::string word;

		bool startingConversion = true;

		while ( ss >> word )
		{
		    if ( word.empty() )
		        continue;

		    // verificar que no sea stop-word
		    if ( stopWordsDictionary.isStopWord( word ) )
		        continue;

		    if ( startingConversion )
		    {
		        outfile << word;
		        startingConversion = false;
		    }
		    else
		        outfile << " " << word;

		    // buscar en el arbol
		    // TODO: bruno aca tendrias que poner la busqueda en el arbol y
		    //  cacular todo lo necesario de las estadísticas...
		}

		outfile << std::endl;
	}

	infile.close();
	outfile.close();
	stopwordsfile.close();

	return 0;
}
